package chainmaker

import (
	"chainmaker.org/chainmaker/common/v2/evmutils"
	"chainmaker.org/chainmaker/pb-go/v2/common"
	"encoding/hex"
	"errors"
	"fmt"
	"io/ioutil"
	"math/big"
	"path/filepath"
	"strings"
	"zero-demo-rpc/chainmaker/cryptogen/config"

	sdk "chainmaker.org/chainmaker/sdk-go/v2"
	"github.com/ethereum/go-ethereum/accounts/abi"
)

// 长安链
type ChainMaker struct {
	cc              *sdk.ChainClient
	myAbi           *abi.ABI
	cryptoGenConfig *config.CryptoGenConfig
	sdkConfPath     string
}

func NewChainMaker(sdkConfPath, nftABIPath, cryptoGenConfPath string) (*ChainMaker, error) {
	cc, err := sdk.NewChainClient(
		sdk.WithConfPath(sdkConfPath),
	)

	if err != nil {
		return nil, err
	}

	// Enable certificate compression
	if cc.GetAuthType() == sdk.PermissionedWithCert {
		if err := cc.EnableCertHash(); err != nil {
			return nil, err
		}
	}

	// 创建 abi
	myAbi, err := newAbi(nftABIPath)
	if err != nil {
		return nil, err
	}

	return &ChainMaker{
		cc:              cc,
		myAbi:           myAbi,
		cryptoGenConfig: config.LoadCryptoGenConfig(cryptoGenConfPath),
		sdkConfPath:     sdkConfPath,
	}, nil
}

// 添加证书
func (cm *ChainMaker) AddCert(signCrt, signKey, tlsCrt, tlsKey string) (certHash string, err error) {
	cc, err := sdk.NewChainClient(
		sdk.WithConfPath(cm.sdkConfPath),
		sdk.WithUserKeyBytes([]byte(tlsKey)),
		sdk.WithUserCrtBytes([]byte(tlsCrt)),
		sdk.WithUserSignKeyBytes([]byte(signKey)),
		sdk.WithUserSignCrtBytes([]byte(signCrt)),
	)
	if err != nil {
		return "", err
	}
	fmt.Println("开始添加证书")

	resp, err := cc.AddCert()

	if err != nil {
		return "", err
	}

	return hex.EncodeToString(resp.ContractResult.Result), nil
}

func (cm *ChainMaker) AddCertByPath(userTlsKeyPath, userTlsCrtPath, userSignKeyPath, userSignCrtPath string) (certHash string, err error) {
	cc, err := sdk.NewChainClient(
		sdk.WithConfPath(cm.sdkConfPath),
		sdk.WithUserCrtFilePath(userTlsCrtPath),
		sdk.WithUserKeyFilePath(userTlsKeyPath),
		sdk.WithUserSignKeyFilePath(userSignKeyPath),
		sdk.WithUserSignCrtFilePath(userSignCrtPath),
	)
	if err != nil {
		return "", err
	}

	resp, err := cc.AddCert()
	if err != nil {
		return "", err
	}

	return hex.EncodeToString(resp.ContractResult.Result), nil
}

// 查询证书
func (cm *ChainMaker) QueryCert(hash []string) (certHash string, err error) {
	certs, err := cm.cc.QueryCert(hash)
	if err != nil {
		return "", err
	}

	if len(certs.GetCertInfos()) != 1 {
		return "", errors.New("no certs")
	}

	return certs.CertInfos[0].GetHash(), nil
}

func (cm *ChainMaker) caKeyPath() string {
	return filepath.Join(cm.cryptoGenConfig.CAPath, "ca.key")
}

func (cm *ChainMaker) caCrtPath() string {
	return filepath.Join(cm.cryptoGenConfig.CAPath, "ca.crt")
}

// 查询合约名称
func (cm *ChainMaker) QueryContract(contractName, method string, timeout int64) ([]interface{}, error) {
	kvs, encodedMethod, err := cm.pack(method)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	result, _, err := cm.invokeUserContractWithResult(contractName, encodedMethod, "", kvs, true)
	if err != nil {
		return nil, err
	}
	return cm.myAbi.Unpack(method, result)
}

// 铸造
func (cm *ChainMaker) Mint(contractName, toAddrInt string, tokenId int64, uri string) ([]interface{}, string, error) {
	method := "mint"
	// addr := evmutils.BigToAddress(evmutils.FromDecimalString(toAddrInt))
	addr := evmutils.BigToAddress(evmutils.FromHexString(toAddrInt[2:]))
	kvs, encodedMethod, err := cm.pack(method, addr, big.NewInt(tokenId), uri)
	if err != nil {
		return nil, "", err
	}

	result, txId, err := cm.invokeUserContractWithResult(contractName, encodedMethod, "", kvs, true)
	if err != nil {
		return nil, txId, err
	}

	unpackedData, err := cm.myAbi.Unpack(method, result)
	if err != nil {
		return nil, txId, err
	}
	return unpackedData, txId, nil
}

// 转赠
func (cm *ChainMaker) SafeTransferFrom(contractName string, signCrt, signKey, tlsCrt, tlsKey, from, to string, tokenId int64) ([]interface{}, string, error) {
	cc, err := sdk.NewChainClient(
		sdk.WithConfPath(cm.sdkConfPath),
		sdk.WithUserKeyBytes([]byte(tlsKey)),
		sdk.WithUserCrtBytes([]byte(tlsCrt)),
		sdk.WithUserSignKeyBytes([]byte(signKey)),
		sdk.WithUserSignCrtBytes([]byte(signCrt)),
	)
	if err != nil {
		return nil, "", err
	}

	return cm.safeTransferFrom(contractName, cc, from, to, tokenId)
}

func (cm *ChainMaker) safeTransferFrom(contractName string, cc *sdk.ChainClient, from, to string, tokenId int64) ([]interface{}, string, error) {
	method := "safeTransferFrom"
	// fromAddress := evmutils.BigToAddress(evmutils.FromDecimalString(from))
	// toAddress := evmutils.BigToAddress(evmutils.FromDecimalString(to))
	fromAddress := evmutils.BigToAddress(evmutils.FromHexString(from[2:]))
	toAddress := evmutils.BigToAddress(evmutils.FromHexString(to[2:]))

	kvs, encodedMethod, err := cm.pack(method, fromAddress, toAddress, big.NewInt(tokenId))
	if err != nil {
		return nil, "", err
	}

	result, txId, err := cm.invokeUserContractWithResultByNewClient(contractName, cc, encodedMethod, "", kvs, true)
	if err != nil {
		return nil, txId, err
	}

	unpackedData, err := cm.myAbi.Unpack(method, result)
	if err != nil {
		return nil, txId, err
	}
	return unpackedData, txId, nil
}

func (cm *ChainMaker) ownerOf(contractName string, tokenId int64) ([]interface{}, string, error) {
	method := "ownerOf"

	kvs, encodedMethod, err := cm.pack(method, big.NewInt(tokenId))
	if err != nil {
		return nil, "", err
	}

	result, txId, err := cm.invokeUserContractWithResult(contractName, encodedMethod, "", kvs, true)
	if err != nil {
		return nil, txId, err
	}

	unpackedData, err := cm.myAbi.Unpack(method, result)
	if err != nil {
		return nil, txId, err
	}
	return unpackedData, txId, nil
}

// 添加管理员
func (cm *ChainMaker) AddManager(contractName string, address string) ([]interface{}, string, error) {
	method := "addManager"
	addr := evmutils.BigToAddress(evmutils.FromHexString(address[2:]))
	kvs, encodedMethod, err := cm.pack(method, addr)
	if err != nil {
		return nil, "", err
	}

	result, txId, err := cm.invokeUserContractWithResult(contractName, encodedMethod, "", kvs, true)
	if err != nil {
		return nil, txId, err
	}

	unpackedData, err := cm.myAbi.Unpack(method, result)
	if err != nil {
		return nil, txId, err
	}
	return unpackedData, txId, nil
}

// 修改转移时间
func (cm *ChainMaker) SetTransferTimeLimit(contractName string, time int64) ([]interface{}, string, error) {
	method := "setTransferTimeLimit"

	kvs, encodedMethod, err := cm.pack(method, big.NewInt(time))
	if err != nil {
		return nil, "", err
	}

	result, txId, err := cm.invokeUserContractWithResult(contractName, encodedMethod, "", kvs, true)
	if err != nil {
		return nil, txId, err
	}

	unpackedData, err := cm.myAbi.Unpack(method, result)
	if err != nil {
		return nil, txId, err
	}
	return unpackedData, txId, nil
}

func (cm *ChainMaker) invokeUserContractWithResult(contractName, method, txId string,
	kvs []*common.KeyValuePair, withSyncResult bool) ([]byte, string, error) {

	contractName = cm.calcContractName(contractName)
	resp, err := cm.cc.InvokeContract(contractName, method, txId, kvs, -1, withSyncResult)
	if err != nil {
		return nil, "", err
	}

	if resp.Code != common.TxStatusCode_SUCCESS {
		return nil, resp.GetTxId(), fmt.Errorf("invoke contract failed, [code:%d]/[msg:%s]\n", resp.Code, resp.Message)
	}

	return resp.ContractResult.Result, resp.GetTxId(), nil
}

func (cm *ChainMaker) invokeUserContractWithResultByNewClient(contractName string, cc *sdk.ChainClient, method, txId string,
	kvs []*common.KeyValuePair, withSyncResult bool) ([]byte, string, error) {

	contractName = cm.calcContractName(contractName)
	resp, err := cc.InvokeContract(contractName, method, txId, kvs, -1, withSyncResult)
	if err != nil {
		return nil, "", err
	}

	if resp.Code != common.TxStatusCode_SUCCESS {
		return nil, resp.GetTxId(), fmt.Errorf("invoke contract failed, [code:%d]/[msg:%s]\n", resp.Code, resp.Message)
	}

	return resp.ContractResult.Result, resp.GetTxId(), nil
}

// 计算合约名称
func (cm *ChainMaker) calcContractName(contractName string) string {
	name := hex.EncodeToString(evmutils.Keccak256([]byte(contractName)))[24:]
	return name
}

func (cm *ChainMaker) pack(method string, args ...interface{}) (kvs []*common.KeyValuePair, decodeMethod string, err error) {
	b, err := cm.myAbi.Pack(method, args...)
	if err != nil {
		return nil, "", err
	}
	datastring := hex.EncodeToString(b)
	kvs = []*common.KeyValuePair{
		{
			Key:   "data",
			Value: []byte(datastring),
		},
	}
	return kvs, datastring[0:8], nil
}

func newAbi(nftABIPath string) (*abi.ABI, error) {
	abiJson, err := ioutil.ReadFile(nftABIPath)
	if err != nil {
		return nil, err
	}
	myAbi, err := abi.JSON(strings.NewReader(string(abiJson)))
	if err != nil {
		return nil, err
	}
	return &myAbi, nil
}
